﻿using System;
using System.Collections.Generic;
using System.Linq;
using Liphofra.Core;

namespace Liphofra.Mvvm.ViewModels
{
    /// <summary>
    /// This is a dynamic view model implementation that only needs to be instantiated once and then can retrieve
    /// all sorts of view models using their type names. Due to limitations of Windows Phone binding has to be done using indexer syntax, e.g.:
    /// <code>DataContext="{Binding [MainViewModel], Source={StaticResource DynamicViewModelLocator}}"</code>
    /// </summary>
    public class DynamicViewModelLocator
    {
        private readonly Dictionary<string, Type> _mappings = new Dictionary<string, Type>();

        private IEnumerable<Type> _appDomainTypes;
        private IServiceLocator _serviceLocator;

        /// <summary>
        /// Gets or sets the service locator.
        /// If no value is set by derived classes, the property is lazy-initialized to <see cref="Liphofra.Core.ServiceLocator.Instance"/>.
        /// </summary>
        protected IServiceLocator ServiceLocator
        {
            get
            {
                if (_serviceLocator == null)
                {
                    _serviceLocator = Liphofra.Core.ServiceLocator.Instance;
                }

                return _serviceLocator;
            }

            set
            {
                _serviceLocator = value;
            }
        }

        /// <summary>
        /// Gets a new view model by specifying the type name as key.
        /// Important: this is a violation of the property paradigm, however it is required to serve the purpose of this class
        /// (i.e. making data binding simpler by removing the need for a view model locator per view model type).
        /// </summary>
        /// <param name="key">The (partial) type name of the view model.</param>
        /// <returns>A new instance of the requested view model type, created and pre-configured by the view model factory.</returns>
        public object this[string key]
        {
            get
            {
                return RetrieveViewModel(key);
            }
        }

        private object RetrieveViewModel(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                return null;
            }

            Type type = null;
            if (_mappings.ContainsKey(key))
            {
                type = _mappings[key];
            }
            else
            {
                var partialTypeName = "." + key;

                // cache all types
                if (_appDomainTypes == null)
                {
                    _appDomainTypes = AppDomain.CurrentDomain.GetAssemblies()
                                               .SelectMany(a => a.GetTypes())
                                               .ToList();
                }

                // try to get the type in question
                var types = _appDomainTypes
                    .Where(t => t.FullName.EndsWith(partialTypeName, StringComparison.OrdinalIgnoreCase))
                    .ToList();

                if (types.Count > 1)
                {
                    throw new ArgumentException("Ambiguous name reference. Cannot decide what view model type to provide.");
                }

                type = types.FirstOrDefault();
            }

            if (type != null)
            {
                var factory = ServiceLocator.Resolve<IViewModelFactory>();
                return factory.CreateViewModel(type);
            }

            return null;
        }
    }
}